package cn.sccl.dirms.topo.discovery.service;

import cn.sccl.dirms.topo.discovery.model.DeviceInfo;
import cn.sccl.dirms.topo.discovery.model.DiscoveryConfig;
import cn.sccl.dirms.topo.discovery.model.PortInfo;
import cn.sccl.dirms.topo.discovery.util.Constants;
import cn.sccl.dirms.topo.discovery.util.REUtil;
import cn.sccl.dirms.topo.snmp.mib.*;
import cn.sccl.dirms.topo.snmp.util.SNMPConfig;
import cn.sccl.dirms.topo.snmp.util.SNMPUtils;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.util.TableEvent;

import java.net.InetAddress;
import java.util.*;

public class DiscoveryDeviceInfoService {

    private DiscoveryConfig discoveryConfig;

    public Boolean IS_GET_INTERFACE = true;
    public Boolean IS_GET_ARP = true;
    public Boolean IS_GET_ROUTER = true;
    public Boolean IS_GET_LLDP = true;
    public Boolean IS_GET_CDP = true;
    public Boolean IS_GET_OSPF = true;
    public Boolean IS_GET_STP = true;
    public Boolean IS_GET_DOCFP = true;
    public Boolean IS_GET_BRIDGE = true;
    public Boolean IS_GET_BASEPORT = true;


    /**
     * 发现设备---串行
     *
     * @return
     */
    public DeviceInfo getDeviceBasicInfo(String ip, List<SNMPConfig> configList, DiscoveryConfig discoveryConfig) {
        this.discoveryConfig = discoveryConfig;
        DeviceInfo deviceInfo = new DeviceInfo();
        deviceInfo.setDeviceIP(ip);
        deviceInfo.getDeviceIps().add(ip);

        SNMPUtils snmp = new SNMPUtils();
        System.out.println("IP: " + ip);
        try {
            for (SNMPConfig snmpConfig : configList) {
                try {
                    snmp.start(ip, snmpConfig);
                    if (snmp.isReadable()) {
                        System.out.println("ip:" + ip + "--系统信息");
                        getSystem(deviceInfo, snmp);        //系统信息

                        deviceInfo.setSnmpConfig(snmpConfig);
                        System.out.println("ip:" + ip + "--主MAC地址");
                        getMainMAC(deviceInfo, snmp);       //主MAC地址
                        if (!Constants.DeviceTypeRouter.equals(deviceInfo.getDeviceType())
                                && !Constants.DeviceTypeSwitch.equals(deviceInfo.getDeviceType())) {
                            return null;//非交换机和路由器发直接返回
                        }
                        System.out.println("ip:" + ip + "--IP地址列表");
                        getIpAddressInfo(deviceInfo, snmp); //IP地址列表
                        if (IS_GET_INTERFACE) {
                            System.out.println("ip:" + ip + "--端口信息");
                            getInterfaceInfo(deviceInfo, snmp); //端口信息
                        }
                        if (IS_GET_ARP) {
                            System.out.println("ip:" + ip + "--ARP表");
                            getIpNetToMediaInfo(deviceInfo, snmp);  //ARP表
                        }
                        if (IS_GET_ROUTER) {
                            System.out.println("ip:" + ip + "--router");
                            getIpRouteInfo(deviceInfo, snmp);        //router
                        }
                        if (IS_GET_LLDP) {
                            System.out.println("ip:" + ip + "--LLDP邻居表");
                            getLLDPInfo(deviceInfo, snmp);      //LLDP邻居表
                        }
                        if (IS_GET_CDP) {
                            System.out.println("ip:" + ip + "--邻居表");
                            getCDPInfo(deviceInfo, snmp);       //邻居表（思科）
                        }
                        if (IS_GET_OSPF) {
                            System.out.println("ip:" + ip + "--OSPF信息表");
                            getOspfInfo(deviceInfo, snmp);      //OSPF信息表
                        }
                        if (IS_GET_STP) {
                            System.out.println("ip:" + ip + "--STP生成树协议表");
                            getStpInfo(deviceInfo, snmp);       //STP生成树协议表
                        }
                        if (IS_GET_DOCFP) {
                            System.out.println("ip:" + ip + "--DOCFP信息");
                            getDocFPTableInfo(deviceInfo, snmp);//DOCFP信息
                        }
                        if (IS_GET_BASEPORT) {
                            System.out.println("ip:" + ip + "--BasePort信息");
                            getBasePortInfo(deviceInfo, snmp); //BasePort信息
                        }
                        if (IS_GET_BRIDGE) {
                            System.out.println("ip:" + ip + "--网桥学习信息");
                            getQBbrigeInfo(deviceInfo, snmp);//网桥学习信息
                        }
                        return deviceInfo;
                    } else {
                        continue;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    continue;
                } finally {
                    snmp.end();
                }
            }
            if (discoveryConfig.isUsePing()) {
                InetAddress address = InetAddress.getByName(ip);
                boolean flag = address.isReachable(3000);
                if (flag) {
                    String hostName = address.getCanonicalHostName();
                    deviceInfo.setDeviceIP(ip);
                    deviceInfo.setDeviceName(hostName);
                    deviceInfo.getDeviceIps().add(ip);
                    deviceInfo.setDeviceProtocol(Constants.DeviceProtocolPing);
                    return deviceInfo;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取设备系统描述信息
     *
     * @param deviceInfo
     */
    private void getSystem(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            deviceInfo.setDeviceProtocol(Constants.DeviceProtocolSNMP);
            SystemInfo systemInfo = (SystemInfo) snmp.getObjectInfo(SystemInfo.class);
            if (systemInfo.getSysDesc() != null) {
                deviceInfo.setSystemInfo(systemInfo);
                deviceInfo.setDeviceName(systemInfo.getSysName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取系统主MAC地址
     */
    private void getMainMAC(DeviceInfo deviceInfo, SNMPUtils snmp) {

        try {
            Variable var = snmp.getVariable(Constants.SwitchMacOid);
            String mac = var == null ? null : var.toString();
            if (REUtil.isMac(mac)) {
                deviceInfo.getDeviceMacs().add(mac.toUpperCase());
                deviceInfo.setDeviceMAC(mac.toUpperCase());
                deviceInfo.setDeviceType(Constants.DeviceTypeSwitch);
            } else {
                mac = getHostMAC(deviceInfo, snmp);
                if (mac != null) {
                    deviceInfo.getDeviceMacs().add(mac.toUpperCase());
                    deviceInfo.setDeviceMAC(mac.toUpperCase());
                }
            }
            getDeviceTypeInfo(deviceInfo, snmp);//设备类型
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取设备端口信息
     */
    private void getInterfaceInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<IfEntry> ifEntries = snmp.getTable(IfEntry.class);
            deviceInfo.setIfEntries(ifEntries);
            for (IfEntry ifEntry : ifEntries) {
                String mac = ifEntry.getIfPhysAddress() == null ? "" : ifEntry.getIfPhysAddress();
                if (!REUtil.isMac(mac)) {
                    continue;
                } else {
                    deviceInfo.getDeviceMacs().add(mac.toUpperCase());
                }
                PortInfo portInfo = new PortInfo();
                portInfo.device = deviceInfo;
                portInfo.name = ifEntry.getIfDescr();
                portInfo.port = ifEntry.getIfIndex();
                portInfo.isUseIfIndexPort = true;
                portInfo.portMacList.add(ifEntry.getIfPhysAddress());
                deviceInfo.getPortInfos().add(portInfo);
            }
            PortInfo portInfo = new PortInfo();
            portInfo.device = deviceInfo;
            portInfo.name = "未知端口（用于只知道有相连关系，但是不知道具体哪个端口）";
            portInfo.port = -1;
            portInfo.isUseIfIndexPort = true;
            deviceInfo.getPortInfos().add(portInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取设备ARP表
     */
    private void getIpNetToMediaInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<IpNetToMediaEntry> ipAdEntAddrList = snmp.getTable(IpNetToMediaEntry.class);
            deviceInfo.getIpNetToMediaEntries().addAll(ipAdEntAddrList);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取思科CDP信息
     */
    private void getCDPInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<CdpCacheEntry> cdpCacheEntries = snmp.getTable(CdpCacheEntry.class);
            deviceInfo.getCdpCacheEntries().addAll(cdpCacheEntries);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取交换机STP生成树
     *
     * @param deviceInfo
     */
    private void getStpInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<Dot1dStpPortEntry> dot1dStpPortEntries = snmp.getTable(Dot1dStpPortEntry.class);
            deviceInfo.getDot1dStpPortEntries().addAll(dot1dStpPortEntries);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取交换机OSPF信息
     *
     * @param deviceInfo
     */
    private void getOspfInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<OspfNbrEntry> ospfNbrEntries = snmp.getTable(OspfNbrEntry.class);
            deviceInfo.getOspfNbrEntries().addAll(ospfNbrEntries);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取交换机OSPF信息
     *
     * @param deviceInfo
     */
    private void getIpRouteInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<IpRouteEntry> ipRouteEntries = snmp.getTable(IpRouteEntry.class);
            Iterator<IpRouteEntry> it = ipRouteEntries.iterator();
            while (it.hasNext()) {
                IpRouteEntry ipRouteEntry = it.next();
                if (REUtil.isEffectiveIp(ipRouteEntry.getIpRouteDest()) &&
                        REUtil.isEffectiveIp(ipRouteEntry.getIpRouteNextHop())) {
                    deviceInfo.getIpRouteEntries().add(ipRouteEntry);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取设备IP地址表
     *
     * @param deviceInfo
     */
    private void getIpAddressInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<IpAddrEntry> ipAddrEntries = snmp.getTable(IpAddrEntry.class);
            for (IpAddrEntry ipAddrEntry : ipAddrEntries) {
                if (REUtil.isEffectiveIp(ipAddrEntry.getIpAdEntAddr())) {
                    deviceInfo.getDeviceIps().add(ipAddrEntry.getIpAdEntAddr());
                    deviceInfo.getIpAddrEntries().add(ipAddrEntry);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取设备LLDP邻居表
     */
    private void getLLDPInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<LldpLocPortEntry> lldpLocPortEntries = snmp.getTable(LldpLocPortEntry.class);
            List<LldpRemEntry> lldpRemEntries = snmp.getTable(LldpRemEntry.class);
            Iterator<LldpRemEntry> localIterator = lldpRemEntries.iterator();
            while (localIterator.hasNext()) {
                LldpRemEntry localLLDPRemEntry = localIterator.next();
                String str;
                if (localLLDPRemEntry.getLldpRemChassisIdSubtype() == 4) {
                    str = REUtil.upperMac(localLLDPRemEntry.getLldpRemChassisId());
                    localLLDPRemEntry.setLldpRemChassisId(str);
                }
                if (localLLDPRemEntry.getLldpRemPortIdSubtype() == 4) {
                    str = REUtil.upperMac(localLLDPRemEntry.getLldpRemPortId());
                    localLLDPRemEntry.setLldpRemPortId(str);
                }
                deviceInfo.getLldpRemEntries().add(localLLDPRemEntry);
            }
            deviceInfo.getLldpLocPortEntries().addAll(lldpLocPortEntries);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * snmp
     *
     * @param deviceInfo
     */
    private void getDocFPTableInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<Dot1dTpFdbEntry> dot1dTpFdbEntries = snmp.getTable(Dot1dTpFdbEntry.class);
            Iterator<Dot1dTpFdbEntry> dot1dTpFdbEntryIterator = dot1dTpFdbEntries.iterator();
            while (dot1dTpFdbEntryIterator.hasNext()) {
                Dot1dTpFdbEntry localDot1dTpFdbEntry = dot1dTpFdbEntryIterator.next();
                String dot1dTpFdbAddress = localDot1dTpFdbEntry.getDot1dTpFdbAddress();
                localDot1dTpFdbEntry.setDot1dTpFdbAddress(REUtil.upperMac(dot1dTpFdbAddress));
            }
            boolean bool = false;
            if ((dot1dTpFdbEntries.size() <= 0) && (deviceInfo.getLldpRemEntries().size() <= 0) && (deviceInfo.getCdpCacheEntries().size() <= 0) && (deviceInfo.getDot1dStpPortEntries().size() <= 0)) {
                bool = true;
                Iterator<IpNetToMediaEntry> localObject2 = deviceInfo.getIpNetToMediaEntries().iterator();
                while (localObject2.hasNext()) {
                    IpNetToMediaEntry ipNetToMediaEntry = localObject2.next();
                    Dot1dTpFdbEntry dot1dTpFdbEntry = new Dot1dTpFdbEntry();
                    dot1dTpFdbEntry.setDot1dTpFdbAddress(ipNetToMediaEntry.getIpNetToMediaPhysAddress());
                    dot1dTpFdbEntry.setDot1dTpFdbPort(ipNetToMediaEntry.getIpNetToMediaIfIndex());
                    dot1dTpFdbEntry.setDot1dTpFdbStatus(3);
                    if (ipNetToMediaEntry.getIpNetToMediaType() != 2)
                        dot1dTpFdbEntries.add(dot1dTpFdbEntry);
                }
            }
            Map<Integer, List<String>> hashMap = new HashMap();
            Iterator<Dot1dTpFdbEntry> dot1dTpFdbEntryIterator1 = dot1dTpFdbEntries.iterator();
            List<String> macInfos;
            while (dot1dTpFdbEntryIterator1.hasNext()) {
                Dot1dTpFdbEntry dot1dTpFdbEntry = dot1dTpFdbEntryIterator1.next();
                if (dot1dTpFdbEntry.getDot1dTpFdbStatus() != null && dot1dTpFdbEntry.getDot1dTpFdbStatus() != 2 && (dot1dTpFdbEntry.getDot1dTpFdbStatus() != 4)) {
                    String dot1dTpFdbAddress = dot1dTpFdbEntry.getDot1dTpFdbAddress();
                    deviceInfo.getDot1dTpFdbEntries().add(dot1dTpFdbEntry);
                    macInfos = hashMap.get(dot1dTpFdbEntry.getDot1dTpFdbPort());
                    if (macInfos == null) {
                        macInfos = new ArrayList();
                        hashMap.put(dot1dTpFdbEntry.getDot1dTpFdbPort(), macInfos);
                    }
                    if (REUtil.isMac(dot1dTpFdbAddress) && (!macInfos.contains(dot1dTpFdbAddress)))
                        macInfos.add(dot1dTpFdbAddress);
                }
            }
            Set set = hashMap.entrySet();
            Iterator iterator = set.iterator();
            Map<Integer, String> portNameMap = new HashMap<Integer, String>();
            for (IfEntry ifEntry : deviceInfo.getIfEntries()) {
                portNameMap.put(ifEntry.getIfIndex(), ifEntry.getIfDescr());
            }
            while (iterator.hasNext()) {
                Map.Entry<Integer, List<String>> map = (Map.Entry<Integer, List<String>>) iterator.next();
                if (map == null) continue;
                PortInfo portInfo = new PortInfo();
                portInfo.device = deviceInfo;
                if (map.getKey() != null) {
                    portInfo.port = map.getKey();
                } else {
                    portInfo.port = -1;
                }
                deviceInfo.getPortInfos().remove(portInfo);
                portInfo.name = portNameMap.get(portInfo.port);
                portInfo.isUseIfIndexPort = bool;
                portInfo.portMacList = map.getValue();
                deviceInfo.getPortInfos().add(portInfo);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * snmp
     *
     * @param deviceInfo
     */
    private void getBasePortInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {
            List<Dot1dBasePortEntry> dot1dBasePortEntries = snmp.getTable(Dot1dBasePortEntry.class);
            deviceInfo.getDot1dBasePortEntries().addAll(dot1dBasePortEntries);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * dot1d【Q】tpFdbTable网桥信息---IP-VLAN-MAC-端口
     *
     * @param deviceInfo
     */
    private void getQBbrigeInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        try {

            List<TableEvent> tableEventList = snmp.getEvents(Dot1dQTpFdbEntry.class);
            List<Dot1dQTpFdbEntry> list = new ArrayList<Dot1dQTpFdbEntry>();
            Dot1dQTpFdbEntry dot1dQTpFdbEntry;
            if (tableEventList != null) {
                for (TableEvent tableEvent : tableEventList) {
                    dot1dQTpFdbEntry = new Dot1dQTpFdbEntry();
                    //得到的格式是这样的2.120.0.144.245.250.160.46 = 8
                    //第一位为2 表示该OID对应的值是端口号，为3表示学习状态
                    //第二位为VLAN号
                    //后面6为MAC地址的二进制表示
                    VariableBinding[] variableBinding = tableEvent.getColumns();
                    if (variableBinding == null) {
                        continue;
                    }
                    int value = variableBinding[0].getVariable().toInt();
                    String index = tableEvent.getIndex().toString();
                    String[] oidArray = index.split("\\.");
                    int typeIndex = Integer.valueOf(oidArray[0]);
                    int vlan = Integer.valueOf(oidArray[1]);
                    String mac = index.substring(index.indexOf(oidArray[0] + "." + oidArray[1] + ".") + 2 + oidArray[0].length() + oidArray[1].length());
                    //转换为16进制
                    mac = REUtil.tenArrayString2HexStr(mac, "\\.", ":");
                    //mac地址已存在，不再添加，避免重复
                    for (Dot1dQTpFdbEntry existEntry : list) {
                        if (existEntry.getDot1qTpFdbAddress().equals(mac)) {
                            dot1dQTpFdbEntry = existEntry;
                        }
                    }
                    dot1dQTpFdbEntry.setDot1qTpFdbAddress(mac);
                    dot1dQTpFdbEntry.setDot1qTpFdbId(vlan);
                    //2为端口值
                    if (typeIndex == 2) {
                        dot1dQTpFdbEntry.setDot1qTpFdbPort(value);
                    }
                    //3为学习状态
                    else if (typeIndex == 3) {
                        dot1dQTpFdbEntry.setDot1qTpFdbStatus(value);
                    }

                    list.add(dot1dQTpFdbEntry);
                }
            }
            deviceInfo.setDot1dQTpFdbEntries(list);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * @param deviceInfo
     * @return
     */
    public void getDeviceTypeInfo(DeviceInfo deviceInfo, SNMPUtils snmp) {
        //网关类型
        try {
            int ipForwarding = 0;
            Variable ipForwardingV = snmp.getVariable(Constants.GatewayOid);
            if (ipForwardingV != null) {
                ipForwarding = ipForwardingV.toInt();
            }
            if (1 == ipForwarding) {
                deviceInfo.setDeviceType(Constants.DeviceTypeRouter); //网关
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            Variable printV = snmp.getVariable(Constants.PrinterOid);
            if (printV != null && !printV.toString().equals("noSuchObject")) {
                deviceInfo.setDeviceType(Constants.DeviceTypePrinter); //打印机
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //主机类型
        try {
            List<SoftwareRunEntry> softwareRunEntryList = snmp.getTable(SoftwareRunEntry.class);
            if (softwareRunEntryList != null && softwareRunEntryList.size() > 0) {
                deviceInfo.setDeviceType(Constants.DeviceTypeComputer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取主机MAC信息
     */
    private String getHostMAC(DeviceInfo deviceInfo, SNMPUtils snmp) {
        String mac = null;
        try {
            InterfacesInfo interfacesInfo = (InterfacesInfo) snmp.getObjectInfo(InterfacesInfo.class);
            if (interfacesInfo.getIfNumber() > 0) {
                List<IfEntry> ifEntityList = interfacesInfo.getIfEntry();
                List<IpAddrEntry> ipAddrEntries = snmp.getTable(IpAddrEntry.class);
                int typeIndex = 0;
                for (IpAddrEntry ipAddrEntry : ipAddrEntries) {
                    if (ipAddrEntry.getIpAdEntAddr().equals(deviceInfo.getDeviceIP())) {
                        typeIndex = ipAddrEntry.getIpAdEntIfIndex();
                    }
                }
                if (typeIndex != 0) {
                    for (IfEntry ifEntry : ifEntityList) {
                        if (typeIndex == ifEntry.getIfIndex()) {
                            mac = ifEntry.getIfPhysAddress();
                            break;
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mac;
    }
}
